package edu.kit.ksri.db.mturk.standaloneutils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.StringTokenizer;

import org.apache.log4j.Logger;

/**
 * Copyright (c) 2010-2011 Karlsruhe Institute of Technology (KIT), Germany
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 * 
 * --------------------------------------------------------------------------
 * 
 * Software: CSP/WMV tool for dynamic quality management of microtasks
 * http://www.ksri.kit.edu/Upload/Users/PeopleClouds/HCOMP2011_An_extendable_toolkit_for_managing_quality_of_human-based_eServices.pdf
 * 
 * Organization: Karlsruhe Institute of Technology (KIT), Germany
 * http://www.kit.edu
 * 
 * Project: PeopleClouds project of the Karlsruhe Service Research Institute(KSRI)
 * http://www.ksri.kit.edu/Default.aspx?PageId=818
 * 
 * Contributing authors: David Bermbach, Sandra Rath, Pascal Wichmann, Robert Kern
 * 
 * ---------------------------------------------------------------------------
 * 
 * Purpose: This class edits a persisted list of {@link Worker}s.
 */
public class OfflinePoolManager {

	private static final Logger basiclog = Logger
			.getLogger("basic." + OfflinePoolManager.class);
	
	private static BufferedReader in = new BufferedReader(
			new InputStreamReader(System.in));

	private static HashMap<String, ArrayList<Worker>> workers = new HashMap<String, ArrayList<Worker>>();

	public static void main(String[] args) throws Exception {
		basiclog
				.info("Enter the path of the file containing the worker information.");
		String inputFile;
		try {
			inputFile = in.readLine();
		} catch (IOException e) {
			basiclog.error("Could not read from console.");
			return;
		}
		basiclog.info("Parsing file " + inputFile);
		try {
			workers = readAndParseFile(inputFile);
		} catch (FileNotFoundException e) {
			basiclog.error("File not found: " + inputFile);
			return;
		} catch (IOException e) {
			basiclog.error("Error while reading from file " + inputFile);
			return;
		}
		basiclog.info("Parsed the following pools:");
		listPools();
		Operation op = null;
		boolean running = true;
		while (running) {
			basiclog.info("Please, choose an operation: " + Operation.getOptions());
			try {
				op = Operation.valueOf(in.readLine());
			} catch (IllegalArgumentException e) {
				basiclog.error("Invalid choice");
				continue;
			}
			switch (op) {
			case quit: {
				running = false;
				break;
			}
			case deletePool: {
				deletePool();
				continue;
			}
			case listPools: {
				listPools();
				continue;
			}
			case createPool: {
				createPool();
				continue;
			}
			case deleteWorker: {
				deleteWorker();
				continue;
			}
			case listPool: {
				listPool();
				continue;
			}
			case renamePool: {
				renamePool();
				continue;
			}
			case mergePools: {
				mergePools();
				continue;
			}
			case splitPool: {
				splitPool();
				continue;
			}
			case importPools: {
				importPools();
				continue;
			}
			case findDuplicates: {
				findDuplicates();
				continue;
			}
			case save: {
				save(inputFile);
				continue;
			}
			case saveAs: {
				saveAs();
				continue;
			}
			}
		}

	}

	private static void saveAs() throws Exception {
		basiclog.info("Please, enter the target path and file name.");
		String file = in.readLine();
		File f = new File(file);
		if (f.isDirectory()) {
			basiclog.error(file + " is a directory.");
			return;
		}
		if (f.exists()) {
			save(file);
		} else {
			writeToFile(f);
		}
	}

	/**
	 * @param inputFile
	 */
	private static void save(String inputFile) throws Exception {
		basiclog.info("Do you want to overwrite " + inputFile + "? (yes/no)");
		Boolean yes = parseYesNow(in.readLine());
		if (yes == null || !yes)
			return;
		writeToFile(new File(inputFile));
	}

	private static void writeToFile(File inputFile) throws Exception {
		PrintWriter pw = new PrintWriter(inputFile);
		for (ArrayList<Worker> list : workers.values())
			for (Worker w : list) {
				pw
						.println(w.pool + "," + w.workerid + ","
								+ w.totalassignments + ","
								+ w.correctassignments + ","
								+ w.wrongassignments);
			}
		pw.flush();
		pw.close();
		basiclog.info("Successfully wrote file " + inputFile + ".");
	}

	private static void findDuplicates() throws Exception {
		basiclog.info("Now checking for duplicates...");
		ArrayList<Worker> duplicates = new ArrayList<Worker>();
		int counter = 0;
		for (String key : workers.keySet()) {
			for (Worker w : workers.get(key)) {
				if (duplicates.contains(w))
					continue;
				for (String otherKey : workers.keySet()) {
					for (Worker otherWorker : workers.get(otherKey)) {
						if (duplicates.contains(otherWorker))
							continue;
						if (w.equals(otherWorker) && w != otherWorker) {
							counter++;
							basiclog.info("Found duplicate entry:\n\t" + w + "\n\t"
									+ otherWorker);
							int choice = 0;
							while (true) {
								basiclog.info("Shall we keep the first"
										+ " one or the second one? (1,2)");
								choice = in.read();
								if (choice == '1' || choice == '2')
									break;
							}
							if (choice == 1) {
								duplicates.add(otherWorker);
							} else {
								duplicates.add(w);
							}

						}
					}
				}
			}
		}
		basiclog.info("Identified " + counter
				+ " duplicates. Removing the selected workers.");
		for (Worker w : duplicates) {
			basiclog.info("Removing worker "
					+ w.workerid
					+ " from pool "
					+ w.pool
					+ " "
					+ (workers.get(w.pool).remove(w) ? "successful."
							: "failed."));
		}
		basiclog.info("Resolved a total of " + counter + " duplicates.");
	}

	private static void importPools() throws Exception {
		basiclog.info("Please, enter the path and file name.");
		HashMap<String, ArrayList<Worker>> newones = readAndParseFile(in
				.readLine());
		basiclog.info("Imported " + newones.size()
				+ " pools, now merging with existing pools");
		for (String k : newones.keySet()) {
			ArrayList<Worker> old = workers.put(k, newones.get(k));
			if (old != null) {
				// previous version exists
				ArrayList<Worker> thenew = workers.remove(k);
				workers.put(k + "_new", thenew);
				workers.put(k + "_old", old);
				for (Worker w : thenew)
					w.pool += "_new";
				for (Worker w : old)
					w.pool += "_old";
			}
		}
		basiclog.info("Pools are merged.");
	}

	private static void splitPool() throws Exception {
		basiclog.info("Please, enter the name of the pool you want to split.");
		String name = in.readLine();
		ArrayList<Worker> pool = workers.get(name);
		if (pool == null) {
			basiclog.info("Pool " + name + " does not exist.");
			return;
		}
		basiclog
				.info("Shall the new pool have a fixed correctness or a fixed number of workers? (corr/number)");
		String mode = in.readLine();
		ArrayList<Worker> lower = new ArrayList<Worker>(), higher = new ArrayList<Worker>();
		if (mode.equalsIgnoreCase("corr")) {
			basiclog.info("Please, enter the correctness level"
					+ " which shall be the minimum (but not infimum)"
					+ " of the \"better\" group in percent.");
			double border = Double.parseDouble(in.readLine());
			basiclog.info("Split pool " + name + " at correctness value " + border
					+ "? (yes/no)");
			Boolean yes = parseYesNow(in.readLine());
			if (yes == null || !yes)
				return;
			for (Worker w : pool) {
				if (w.correctness < border) {
					lower.add(w);
					w.pool += "_lower";
				} else {
					higher.add(w);
					w.pool += "_higher";
				}
			}
			workers.remove(name);
			workers.put(name + "_lower", lower);
			workers.put(name + "_higher", higher);
			basiclog.info("Done splitting pools.\n\t" + name + "_lower ("
					+ lower.size() + ")\n\t" + name + "_higher ("
					+ higher.size() + ")");
			return;
		} else if (mode.equalsIgnoreCase("number")) {
			basiclog
					.info("Please, enter the number of workers for the \"better\" group.");
			int number = Integer.parseInt(in.readLine());
			if (number <= 0 || number >= pool.size()) {
				basiclog
						.error("Number may not be <=0 or exceed the original pool's size!");
				return;
			}
			Worker[] array = pool.toArray(new Worker[pool.size()]);
			Arrays.sort(array, null);
			int counter = 0;
			for (Worker w : array) {
				if (counter < number) {
					higher.add(w);
					w.pool += "_higher";
				} else {
					lower.add(w);
					w.pool += "_lower";
				}
				counter++;
			}
			workers.remove(name);
			workers.put(name + "_lower", lower);
			workers.put(name + "_higher", higher);
			basiclog.info("Done splitting pools.\n\t" + name + "_lower ("
					+ lower.size() + ")\n\t" + name + "_higher ("
					+ higher.size() + ")");
			return;

		} else {
			basiclog.error("Invalid choice");
			return;
		}

	}

	private static void mergePools() throws Exception {
		basiclog.info("Please, enter the of the first pool.");
		String first = in.readLine();
		ArrayList<Worker> pool = workers.get(first);
		if (pool == null) {
			basiclog.info("Pool " + first + " does not exist.");
			return;
		}
		basiclog.info("Please, enter the of the second pool.");
		String second = in.readLine();
		ArrayList<Worker> pool2 = workers.get(second);
		if (pool2 == null) {
			basiclog.info("Pool " + second + " does not exist.");
			return;
		}
		String newName = null;
		while (newName == null) {
			basiclog.info("Please, enter the name for the merged pool.");
			newName = in.readLine();
			if (workers.get(newName) != null) {
				basiclog.info("Pool " + newName + " does already exist.");
				newName = null;
			}
		}
		basiclog.info("Are you sure you want to merge the pools " + first + " and "
				+ second + " into the new pool " + newName + "? (yes/no)");
		Boolean yes = parseYesNow(in.readLine());
		if (yes == null || !yes)
			return;
		workers.remove(first);
		workers.remove(second);
		pool.addAll(pool2);
		workers.put(newName, pool);
		for (Worker w : pool)
			w.pool = newName;
		basiclog.info("Merged pools " + first + " and " + second + " into new pool "
				+ newName + ".");
	}

	private static void renamePool() throws Exception {
		basiclog.info("Please, enter the name of the pool you want to rename.");
		String name = in.readLine();
		ArrayList<Worker> pool = workers.get(name);
		if (pool == null) {
			basiclog.info("Pool " + name + " does not exist.");
			return;
		}
		basiclog.info("Please, enter the new name.");
		String newName = in.readLine();
		if (newName.contains("_")) {
			basiclog.error("Pool names may not include the character \"_\".");
			return;
		}
		workers.remove(name);
		workers.put(newName, pool);
		for (Worker w : pool)
			w.pool = newName;
		basiclog.info("Pool " + name + " renamed to " + newName + ".");
	}

	private static void listPool() throws Exception {
		basiclog.info("Please, enter the name of the pool you want to list.");
		String name = in.readLine();
		ArrayList<Worker> pool = workers.get(name);
		if (pool == null)
			basiclog.info("Pool " + name + " does not exist.");
		else {
			for (Worker w : pool)
				basiclog.info(w);
		}
	}

	private static void deleteWorker() throws Exception {
		basiclog.info("Enter the id of the worker you want to delete.");
		String id = in.readLine();
		basiclog.info("Searching for worker " + id + ".");
		String poolname = findWorkerInPools(id);
		if (poolname == null) {
			basiclog.info("The specified worker does not exist.");
			return;
		}
		basiclog.info("Are you sure you want to delete worker " + id + " from pool "
				+ poolname + "? (yes/no)");
		Boolean yes = parseYesNow(in.readLine());
		if (yes == null || yes == false)
			return;
		basiclog.info("Deleting worker "
				+ (workers.get(poolname).remove(new Worker(id)) ? "successful."
						: "failed."));

	}

	private static String findWorkerInPools(String workerid) {
		for (String s : workers.keySet()) {
			for (Worker w : workers.get(s)) {
				if (w.workerid.equals(workerid))
					return s;
			}
		}
		return null;
	}

	private static void createPool() throws Exception {
		basiclog.info("Please, enter the name of the new pool.");
		String name = in.readLine();
		ArrayList<Worker> old = workers.get(name);
		if (old != null) {
			basiclog.error("Pool " + name + " already exists.");
			return;
		}
		if (name.contains("_")) {
			basiclog.error("Pool names may not include the character \"_\".");
			return;
		}
		basiclog.info("Add pool " + name + "? (yes/no)");
		Boolean res = parseYesNow(in.readLine());
		if (res == null || res == false)
			return;
		workers.put(name, new ArrayList<Worker>());
		basiclog.info("Adding pool " + name + " was successful.");
	}

	/**
	 * @param inputFile
	 * @return
	 */
	private static HashMap<String, ArrayList<Worker>> readAndParseFile(
			String inputFile) throws Exception {
		HashMap<String, ArrayList<Worker>> workers = new HashMap<String, ArrayList<Worker>>();
		ArrayList<Worker> al = null;
		BufferedReader br = new BufferedReader(new FileReader(inputFile));
		String line = br.readLine();
		while (line != null) {
			if (line.trim().startsWith("#")){
				line = br.readLine();
				continue;}
			StringTokenizer st = new StringTokenizer(line, ",");
			Worker w = new Worker();
			w.pool = st.nextToken();
			w.workerid = st.nextToken();
			w.totalassignments = Integer.parseInt(st.nextToken());
			w.correctassignments = Integer.parseInt(st.nextToken());
			w.wrongassignments = Integer.parseInt(st.nextToken());
			w.correctness = 100.0 * (w.totalassignments - w.wrongassignments)
					/ (double) w.totalassignments;
			al = workers.get(w.pool);
			if (al == null) {
				al = new ArrayList<Worker>();
				workers.put(w.pool, al);
			}
			al.add(w);
			line = br.readLine();
		}
		return workers;
	}

	private static void listPools() {
		for (String s : workers.keySet()) {
			basiclog.info(s + " - " + workers.get(s).size() + " worker(s)");
		}
	}

	private static void deletePool() throws Exception {
		basiclog.info("Type in the pool which shall be deleted");
		String pool = in.readLine();
		ArrayList<Worker> p = workers.get(pool);
		if (p == null) {
			basiclog.error("Pool " + pool + " does not exist.");
			return;
		}
		boolean deleteAll = false;
		basiclog
				.info("Do you want to delete all workers"
						+ " from that pool or move them"
						+ " to another? (move/delete)");
		String del = in.readLine();
		if (del.equalsIgnoreCase("delete"))
			deleteAll = true;
		else if (del.equalsIgnoreCase("move"))
			deleteAll = false;
		else {
			basiclog.error("Invalid choice");
			return;
		}
		if (deleteAll) {
			basiclog.info("Are you sure you want to delete " + p.size()
					+ " workers? (yes/no)");
			del = in.readLine();
			Boolean b = parseYesNow(del);
			if (b == null || !b)
				return;
			if (b) {
				basiclog.info("Deleting pool " + pool + " with " + p.size()
						+ " workers.");
				workers.remove(pool);
			}
		} else {
			basiclog.info("Type in the target pool's name");
			String target = in.readLine();
			if (target.equals(pool)) {
				basiclog.error("Pools are identical");
				return;
			}
			ArrayList<Worker> t = workers.get(target);
			if (t == null) {
				basiclog.error("Pool " + target + " does not exist.");
				return;
			}
			basiclog.info("Are you sure you want to move " + p.size()
					+ " workers from pool " + pool + " to pool " + target
					+ "? (yes/no)");
			Boolean b = parseYesNow(in.readLine());
			if (b == null || !b)
				return;
			basiclog.info("Moving " + p.size() + " workers and deleting pool "
					+ pool + ".");
			t.addAll(p);
			workers.remove(pool);
			return;
		}
	}

	private static Boolean parseYesNow(String s) {
		if (s.equalsIgnoreCase("yes"))
			return true;
		if (s.equalsIgnoreCase("no"))
			return false;
		basiclog.error("Invalid choice");
		return null;
	}

	public static class Worker implements Comparable<Worker> {
		public String workerid;
		public String pool;
		public int totalassignments;
		public int correctassignments;
		public int wrongassignments;
		public double correctness;

		public Worker() {
		}

		public Worker(String id) {
			workerid = id;
		}

		@Override
		public boolean equals(Object obj) {
			if (!(obj instanceof Worker))
				return false;
			return workerid.equals(((Worker) obj).workerid);
		}

		@Override
		public int hashCode() {
			return workerid.hashCode();
		}

		@Override
		public String toString() {
			return workerid + " from pool " + pool + ", " + totalassignments
					+ " assignments (" + correctassignments + " correct/"
					+ wrongassignments + " wrong); correctness = "
					+ correctness + "%";
		}

		public int compareTo(Worker b) {
			if (correctness < b.correctness)
				return 1;
			if (correctness > b.correctness)
				return -1;
			if (correctassignments < b.correctassignments)
				return 1;
			if (correctassignments > b.correctassignments)
				return -1;
			if (totalassignments < b.totalassignments)
				return 1;
			if (totalassignments > b.totalassignments)
				return -1;
			return 0;

		}

	}

	public static enum Operation {
		importPools, listPools, listPool, deletePool, deleteWorker, createPool, renamePool, mergePools, splitPool, findDuplicates, save, saveAs, quit;

		public static String getOptions() {
			String res = "";
			int counter = 0;
			for (Operation s : Operation.values()) {
				counter++;
				res += s + ",";
				if (counter > 5) {
					res += "\n\t\t\t       ";
					counter = 0;
				}
			}
			res = res.trim();
			res = res.substring(0, res.length() - 1);
			return res;
		}
	}

}
